import Constants
from Genetic import Population, Pattern, Condition
import shelve
from math import log10, exp
from numpy import std
from random import seed

class Parameters():
    """The Parameters class holds all the evolutionary and options parameters that control the program"""
    def __init__(self):
        """Initialize the Parameters class."""
        self.n_gen = Constants.DefGenerations
        self.size = Constants.DefPopulationSize
        self.freq = Constants.DefStatusFrequency
        self.c_hor = Constants.DefControlHorizon
        self.p_hor = Constants.DefPredictHorizon
        self.init_range = Constants.DefInitRange
        self.init_mean = Constants.DefInitMean
        self.mut_range_rate = Constants.DefMutRangeRate
        self.mut_mean_rate = Constants.DefMutMeanRate
        self.frac_discard = Constants.DefFracDiscard
        self.mut_prob = Constants.DefMutProb
        self.ideal_matches = Constants.DefIdealMatches
        self.seed = Constants.DefRandSeed
    

class Container():
    """The Container class encapsulates the entire program, containing the population, evolution controls, status message generators, and more."""
    def __init__(self, parameters, data):
        """Initialize the Container class."""
        self.p = parameters
        self.data = Data(self.p, data)
        self.population = Population(self.p, self.data)
        self.reset()
    
    def reset(self):
        """Reset the container to a starting configuration."""
        self.index = 0
        #seed(self.p.seed)
        # re-initialize population to a random state
    
    def status(self):
        """Print a status message about the population and its current state."""
        #self.best = self.population.best()
        print self.population.best_pattern
        #print('Generation %d: Best = %.4f' % (self.index, self.best.F))
    
    def report(self):
        """Generate a full report on the population in its current state"""
        pass
    
    def run(self):
        """Start the evolution"""
        while (self.index <= self.p.n_gen):
            self.index += 1
            self.population.step()
            if (self.index % self.p.freq == 0):
                self.status()
    

class Data():
    """The Data class holds the training data to which patterns are being sought."""
    def __init__(self, parameters, data):
        """Initialize the Data class."""
        self.p = parameters
        self.raw = data
        self.train = self._gen_train()
    
    def _gen_train(self):
        """Generate a set of training points based on the raw data array.  This implementation does not scale data points to be relative to the initial point, or any other tricks."""
        train = []
        y_set = []
        num_data = len(self.raw) - (self.p.c_hor + self.p.p_hor) + 1
        for i in range(num_data):
            row = self.raw[i:i + self.p.c_hor]
            #row.append(self.raw[i + self.p.c_hor + self.p.p_hor - 1])
            train.append([row, self.raw[i + self.p.c_hor + self.p.p_hor - 1]])
            y_set.append(train[i][1])
        self.sigma_o = std(y_set)
        return train
    
    def fitness2(self, pattern):
        return 1.0
    
    def fitness(self, pattern):
        """Evaluate the fitness of the given pattern."""
        y_set = []
        i = 0
        while i < len(self.train):
            if pattern.check(self.train[i][0]) == True:
                y_set.append(self.train[i][1])
                i += 5
            else:
                i += 1
        if len(y_set) <= 1:
            return 0
        else:
            term1 = -log10(std(y_set) / self.sigma_o)
            term2 = 1./(1+exp(-len(y_set)+self.p.ideal_matches-1)) - 1./(1+exp(-len(y_set)+self.p.ideal_matches+1))
            #fitness = -log10(std(y_set) / self.sigma_o) - self.alpha / len(y_set)
            #print term1
            fitness = term1 + term2
            if fitness < 0:
                return 0
            else:
                return fitness
    



